﻿using Kalman;
using Kalman.Jwt;
using Kalman.Security;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;

namespace QuDao
{
    public class ApiUtils : SingletonBase<ApiUtils>
    {
        readonly StringCollection sc;
        readonly JwtBuilder jwt;
        public ApiUtils() : base()
        {
            sc = new StringCollection();
            jwt = new JwtBuilder();
        }

        /// <summary>
        /// 获取接口调用令牌
        /// </summary>
        /// <param name="ak">应用程序标志</param>
        /// <returns></returns>
        public JwtToken GetToken(string ak)
        {
            var token = new JwtToken
            {
                AccessToken = jwt.SetJwtID<string>(ak).Encode(),
                RefreshToken = jwt.Encode(true)
            };
            return token;
        }

        /// <summary>
        /// 根据刷新令牌获取新的令牌数据（如果刷新令牌也过期的话，跳到登录界面重新获取令牌）
        /// </summary>
        /// <param name="refreshToken">刷新令牌</param>
        /// <returns></returns>
        public JwtToken GetTokenByRefresh(string refreshToken)
        {
            var dic = jwt.Decode(refreshToken, true);
            if (!dic.ContainsKey("refresh")) throw new AppException("无效的刷新令牌", "401");

            var ak = dic["jti"].ToString();
            var token = new JwtToken
            {
                AccessToken = jwt.SetJwtID<string>(ak).Encode(),
                RefreshToken = jwt.Encode(true)
            };
            return token;
        }

        /// <summary>
        /// 检查令牌
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public AppException Check(string token)
        {
            return jwt.Check(token);
        }

        /// <summary>
        /// 检查Nonce字符串
        /// </summary>
        /// <param name="nonce">只能使用一次的字符串</param>
        /// <returns></returns>
        public void CheckNonce(string nonce)
        {
            if (sc.Contains(nonce)) throw new ApiException(400, "检测到重复的请求");
            if (sc.Count > 1000000) sc.Clear(); //缓存数据达到100万自动清理，100万32位长度字符串大概占用16M左右内存

            sc.Add(nonce);
        }

        ///// <summary>
        ///// 获取API签名
        ///// </summary>
        ///// <param name="method">请求方式（大写）：GET，POST，PUT，DELETE</param>
        ///// <param name="url">请求资源地址，不包含Host地址并排除问号后面的所有参数，例如：https://api.xxx.com/system/gettime?format=yyyyMMdd ResourceUrl为"system/gettime"</param>
        ///// <param name="ak">应用程序标志</param>
        ///// <param name="ts">Unix时间戳字符串</param>
        ///// <param name="nonce">随机字符串</param>
        ///// <param name="body">请求内容</param>
        ///// <param name="sk">应用安全秘钥SecretKey</param>
        ///// <returns></returns>
        //public string GetSign(string method, string url, string body, string ts, string nonce, string sk)
        //{
        //    /*
        //     * 计算公式：sign = SHA1({HttpMethod}+"\n"+{Url}+"\n"+MD5({Body})+"\n"+{ak}+"\n"+{ts}+"\n"+{nonce}+"\n"+{sk})
        //     SHA1,MD5	哈希算法，计算结果转小写
        //     HttpMethod	统一为"POST"，大写
        //     Url			请求地址，排除问好后面的所有参数
        //     Body		请求内容，如果是上传文件MD5({Body})为空字符串
        //     sk			SecretKey，应用安全秘钥，每个应用都会分配一个
        //     */
        //    var bodyMD5 = string.Empty;
        //    if(!string.IsNullOrEmpty(body))bodyMD5 = HashCryto.GetHash2String(body, HashAlgorithmType.MD5);

        //    var s = $"{method}\n{url}\n{bodyMD5}\n{ak}\n{ts}\n{nonce}\n{sk}";

        //    return HashCryto.GetHash2String(s, HashAlgorithmType.SHA1).ToLower();

        //}

        /// <summary>
        /// 获取签名（小写）
        /// </summary>
        /// <param name="dic"></param>
        /// <param name="secretKey"></param>
        /// <returns></returns>
        public string GetSign(IDictionary<string, string> dic, string secretKey)
        {
            var sortDic = new SortedDictionary<string, string>(dic);

            var sb = new StringBuilder();

            foreach (var item in sortDic)
            {
                if (item.Key.ToLower() == "sign" || item.Key.ToLower() == "signature") continue;
                sb.Append(item.Value);
            }

            sb.Append(secretKey);

            string s = HashCryto.GetHash2String(sb.ToString(), HashAlgorithmType.MD5, Encoding.UTF8);
            return s.ToLower();
        }
    }
}
